#pragma once
#include "zMatrixBase.hpp"

//for mat1+mat2, two matricies

namespace zzz{
#define MATRIX_EXPRESSION(name,ope) \
template<typename T,class Major>\
class zMatrix##name##Expression : public zMatrixBaseR<T,Major>\
{\
public:\
  explicit zMatrix##name##Expression(const zMatrixBaseR<T,Major> &mat1, const zMatrixBaseR<T,Major> &mat2)\
  :zMatrixBaseR<T,Major>(mat1.rows_,mat1.cols_),Mat1_(mat1),Mat2_(mat2)\
  {\
    ZCHECK_EQ(mat1.rows_, mat2.rows_); \
    ZCHECK_EQ(mat1.cols_, mat2.cols_); \
  }\
  using zMatrixBaseR<T,Major>::operator(); \
  const T operator()(zuint r, zuint c) const\
  {\
    CheckRange(r, c); \
    return (Mat1_(r,c) ope Mat2_(r,c)); \
  }\
public:\
  const zMatrixBaseR<T,Major> &Mat1_, &Mat2_; \
};

MATRIX_EXPRESSION(Plus,+)
MATRIX_EXPRESSION(Minus,-)
MATRIX_EXPRESSION(Times,*)
MATRIX_EXPRESSION(DividedBy,/)

template<typename T,class Major>
const zMatrixPlusExpression<T,Major> operator+(const zMatrixBaseR<T,Major> &mat1, const zMatrixBaseR<T,Major> &mat2)
{return zMatrixPlusExpression<T,Major>(mat1,mat2);}

template<typename T,class Major>
const zMatrixMinusExpression<T,Major> operator-(const zMatrixBaseR<T,Major> &mat1, const zMatrixBaseR<T,Major> &mat2)
{return zMatrixMinusExpression<T,Major>(mat1,mat2);}

template<typename T,class Major>
const zMatrixTimesExpression<T,Major> DotTimes(const zMatrixBaseR<T,Major> &mat1, const zMatrixBaseR<T,Major> &mat2)
{return zMatrixTimesExpression<T,Major>(mat1,mat2);}

template<typename T,class Major>
const zMatrixDividedByExpression<T,Major> DotDividedBy(const zMatrixBaseR<T,Major> &mat1, const zMatrixBaseR<T,Major> &mat2)
{return zMatrixDividedByExpression<T,Major>(mat1,mat2);}

MATRIX_EXPRESSION(EQ,==)
MATRIX_EXPRESSION(NE,!=)
MATRIX_EXPRESSION(LT,<)
MATRIX_EXPRESSION(LE,<=)
MATRIX_EXPRESSION(GT,>)
MATRIX_EXPRESSION(GE,>=)

template<typename T,class Major>
const zMatrixEQExpression<T,Major> operator==(const zMatrixBaseR<T,Major> &mat1, const zMatrixBaseR<T,Major> &mat2)
{return zMatrixEQExpression<T,Major>(mat1,mat2);}

template<typename T,class Major>
const zMatrixNEExpression<T,Major> operator!=(const zMatrixBaseR<T,Major> &mat1, const zMatrixBaseR<T,Major> &mat2)
{return zMatrixNEExpression<T,Major>(mat1,mat2);}

template<typename T,class Major>
const zMatrixLTExpression<T,Major> operator<(const zMatrixBaseR<T,Major> &mat1, const zMatrixBaseR<T,Major> &mat2)
{return zMatrixLTExpression<T,Major>(mat1,mat2);}

template<typename T,class Major>
const zMatrixLEExpression<T,Major> operator<=(const zMatrixBaseR<T,Major> &mat1, const zMatrixBaseR<T,Major> &mat2)
{return zMatrixLEExpression<T,Major>(mat1,mat2);}

template<typename T,class Major>
const zMatrixGTExpression<T,Major> operator>(const zMatrixBaseR<T,Major> &mat1, const zMatrixBaseR<T,Major> &mat2)
{return zMatrixGTExpression<T,Major>(mat1,mat2);}

template<typename T,class Major>
const zMatrixGEExpression<T,Major> operator>=(const zMatrixBaseR<T,Major> &mat1, const zMatrixBaseR<T,Major> &mat2)
{return zMatrixGEExpression<T,Major>(mat1,mat2);}

template<typename T,class Major>
class zMatrixProductExpression : public zMatrixBaseR<T,Major>
{
public:
  explicit zMatrixProductExpression(const zMatrixBaseR<T,Major> &mat1, const zMatrixBaseR<T,Major> &mat2)
  :zMatrixBaseR<T,Major>(mat1.rows_,mat2.cols_),Mat1_(mat1),Mat2_(mat2),commonLength_(mat1.cols_)
  {
    ZCHECK_EQ(mat1.cols_, mat2.rows_);
  }
  using zMatrixBaseR<T,Major>::operator();
  const T operator()(zuint r, zuint c) const
  {
    CheckRange(r, c);
    T v=0;
    for (zuint i=0; i<commonLength_; i++)
      v+=Mat1_(r,i)*Mat2_(i,c);
    return v;
  }
public:
  const zMatrixBaseR<T,Major> &Mat1_, &Mat2_;
  const zuint commonLength_;
};

template<typename T,class Major>
const zMatrixProductExpression<T,Major> operator*(const zMatrixBaseR<T,Major> &mat1, const zMatrixBaseR<T,Major> &mat2)
{return zMatrixProductExpression<T,Major>(mat1,mat2);}

}
